﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;

namespace ReferenceSwap
{
    /// <summary>
    /// Visual Studio Assembly Reference
    /// </summary>
    public class AssemblyReference : Reference
    {
        Dictionary<string, string> assemblyMetaData;

        /// <summary>
        /// Create an instance of an Assembly Reference
        /// </summary>
        public AssemblyReference()
        {
            assemblyMetaData = new Dictionary<string, string>();

            assemblyMetaData.Add("Version", "1.0.0.0");
            assemblyMetaData.Add("Culture", "neutral");
            assemblyMetaData.Add("processorArchitecture", "MSIL");
        }

        /// <summary>
        /// Create an instance of an Assembly Reference
        /// use an actual assembly to create reference information
        /// </summary>
        /// <param name="pathToAssembly">Path to the assembly</param>
        public AssemblyReference(string pathToAssembly)
            : this()
        {
            Guard.Against<ArgumentException>(string.IsNullOrEmpty(pathToAssembly), "PathToAssembly must have a valid value.");
            Guard.Against<FileNotFoundException>(!File.Exists(pathToAssembly), string.Format("Unable to find assembly file: {0}.", pathToAssembly));

            Path = pathToAssembly;

            ParseFullName(AssemblyName.GetAssemblyName(pathToAssembly).FullName);
        }
        /// <summary>
        /// Gets the Assembly Meta Data
        /// </summary>
        public Dictionary<string, string> MetaData
        {
            get { return assemblyMetaData; }
        }
        /// <summary>
        /// Gets or Sets if should refer only to a Specific Version
        /// </summary>
        public bool SpecificVersion { get; set; }

        /// <summary>
        /// Parse the Name information
        /// </summary>
        /// <param name="include">include path</param>
        public void ParseFullName(string include)
        {
            // use the assembly name class to parse
            AssemblyName name = new AssemblyName(include);

            Name = name.Name;

            string publicKeyToken = Util.BytesToHex(name.GetPublicKeyToken());
            if (!string.IsNullOrEmpty(publicKeyToken))
                MetaData.Add("PublicKeyToken", publicKeyToken);

            // set some defaults
            if (name.Version != null)
                MetaData["Version"] = name.Version.ToString();

            if (name.CultureInfo != null)
                MetaData["Culture"] = string.IsNullOrEmpty(name.CultureInfo.ToString()) ? "neutral" : name.CultureInfo.ToString();

            MetaData["processorArchitecture"] = name.ProcessorArchitecture == ProcessorArchitecture.None ? "MSIL" : name.ProcessorArchitecture.ToString();
        }

        /// <summary>
        /// Create a Visual studio project xml representation of a Assembly Reference item
        /// </summary>
        /// <returns>xml string</returns>
        public override string GenerateXml()
        {
            XmlDocument xml = new XmlDocument();
            XmlElement reference = xml.CreateElement("Reference");

            XmlAttribute include = xml.CreateAttribute("Include");
            // not sure that this will work - will the order mess things up?
            // might be better to just save the "AssemblyName" class and use that to rebuild here...
            StringBuilder assemblyData = new StringBuilder();
            assemblyData.Append(Name);

            foreach (var data in assemblyMetaData)
                assemblyData.AppendFormat(", {0}={1}", data.Key, data.Value);

            include.Value = assemblyData.ToString();
            reference.Attributes.Append(include);

            XmlElement vers = xml.CreateElement("SpecificVersion");
            vers.InnerText = SpecificVersion.ToString();
            reference.AppendChild(vers);

            XmlElement hint = xml.CreateElement("HintPath");
            hint.InnerText = Path;
            reference.AppendChild(hint);

            return reference.OuterXml;
        }
    }
}
